Bean Validation と契約による設計
問題
ドメインオブジェクトのフィールドに制約アノテーションをつけるのはどうなのか?
ドメインオブジェクトのjavax.validationへの依存
利用者側がバリデーションの効かない環境で生成できてしまう
つまり、不変条件を満たさないインスタンスが生じてしまうリスクがある ルーチンの本体で検証をする vs 事前条件として表現する
Eiffelでは、表現された事前条件はrequireレベル以上で実行時に監視される
require以上のレベルに設定していれば、ルーチン本体での検証は不要
表明を実行時に監視する機構を持たない言語(Java、PHPなど)の場合
利用者の責任だとしてルーチン本体で検証はしない
利用者を信頼せずにルーチン本体で検証する
違反していた場合は、利用者のバグが発見されたということになる(事前条件) assertでやるか、通常の制御構造でやるか
assertは実行時にオフにできる
開発・テスト過程での利用に留めたい場合に使う
通常の制御構造はオフにできない(必ず検査される)
開発・テスト過程でも使えるが、プロダクションコードにも混入する
assertをオフにしてもなお不安なときに使う
発生した場合はやはりバグ。
システムを落とす必要がある。
PHPではLogicException
開発者を信頼できないとき?
入力チェックにおける制御構造とは別。
ユーザーからの入力ではシステムは落とさない。
PHPではRuntimeException
Bean Validation
Bean Validation を メソッドの事前条件事後条件に使う場合
Springのmethod validationの仕組みを使う場合
@Validatedを書くのは利用者
制約アノテーション(ex. @NotNull)を書くのも利用者
ただし、@javax.validation.Validを書けば、供給者のフィールドも検証の対象になる
これは、供給者のクラスがJavaBeanの場合
つまり、アクセッサ(getter)が必要
自分で検証
これは現実的ではない
生成されたオブジェクトを Validation.buildDefaultValidatorFactory().validator.validate(obj)する
入力チェックと事前条件
事前条件は、ルーチンとルーチンとの間のもの
ユーザーやネットワークからの信頼できない入力を扱うのが入力チェック
これはif ... then ... + 例外スローで対応するほかない
入力チェックモジュールは、自分が呼び出すルーチンの事前条件が満たされるようにする責任を負う 関連情報
Bean Validation は、公式ドキュメントによれば、契約による設計を可能にする。
事前条件(引数)と事後条件(戻り値)の保証に使える
検証ロジックは@Constraint(validatedBy = {HogeValidator.class})で指定したクラスで実装
implements javax.validation.ConstraintValidator
public boolean isValid(T e, ConstraintValidatorContext context)
使い方
アノテーション(@Constraintを付与した@interface)を検証対象に付与
検証対象の候補(enum ElementType)
Type FIELD METHOD PARAMETER CONSTRUCTOR LOCAL_VARIABLE ANNOTATION_TYPE PACKAGE TYPE_PARAMETER TYPE_USE
javax.validation.Validationのデフォルト実装がjava.util.ServiceLoaderを使ってライブラリをロードしてくれる
Springの場合、(JSR380の参照実装でもある)Hibernate Validatorをロード
アノテーションを付与しただけでは動かない
基本的には、検証を実行するBeanに、java.validation.Validatorを注入して利用する
たとえば、@Serviceを付与したサービスクラスに@Autowired private Validator validatorする
Spring駆動のメソッド検証は、org.springframework.validation.annotation.Validatedアノテーションを付与する
@ValidatedのTargetは、TYPE, METHOD, PARAMETERの3種類
Spring ValidationはAOPの仕組みを使っているので、コンストラクタには使えないらしい
同じクラス内のpublicメソッドから呼び出されるときも効かないっぽい
ということは、privateメソッドはバリデーション効かないのでは
これは、それを呼び出すpublicメソッド側でなんとかしてあげればよさげ。契約だし。
前提として、@ConfigurationつきのAppConfigで、MethodValidationPostProcessorをBean登録する
Bean Validationとは別のバリデーションとして、springのValidatorインタフェースも利用可能
code:java
package com.example.domain.service;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull;
import javax.validation.Valid
@Validated
public interface HelloService {
// (2) helloメソッドの返り値がnullになると例外スロー
@NotNull
String hello(@NotNull /* (1) 引数messageにnullが渡されると例外スロー */ String message);
// Java Bean()
@NotNull // (3) helloメソッドの返り値がnullになると例外スロー
@Valid // (4) helloメソッドの返り値のHelloOutput型のオブジェクトのフィールドに付与された制約が有効化される
HelloOutput hello(
@NotNull /* (1) 引数inputにnullが渡されると例外スロー*/
@Valid /* (2) HelloInput型の引数inputのフィールドに付与された制約が有効化される*/
HelloInput input);
}
資料
JSR380
Bean Validation 1.1 allows to put constraints to the parameters and return values of arbitrary methods and constructors. That way the Bean Validation API can be used to describe and validate the contract applying to a given method or constructor, that is:
the preconditions that must be met by the caller before the method or constructor may be invoked and
the postconditions that are guaranteed to the caller after a method or constructor invocation returns.
This enables a programming style known as Programming by Contract (PbC). Compared to traditional means of checking the sanity of argument and return values this approach has several advantages:
These checks are expressed declaratively and don’t have to be performed manually, which results in less code to write, read and maintain.
The pre- and postconditions applying for a method or constructor don’t have to be expressed again in the documentation, since any of its annotations will automatically be included in the generated JavaDoc. This reduces redundancies, thus avoiding efforts and inconsistencies between implementation and documentation.
参考